/*
 * Huawei HiNIC PCI Express Linux driver
 * Copyright(c) 2017 Huawei Technologies Co., Ltd
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 */
#define pr_fmt(fmt) KBUILD_MODNAME ": [NIC]" fmt

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/ethtool.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/module.h>

#include "ossl_knl.h"
#include "hinic3_crm.h"
#include "hinic3_hw.h"
#include "hinic3_common.h"
#include "hinic3_nic.h"
#include "hisec_mpu_cmd.h"
#include "hisec_mpu_cmd_defs.h"
#include "hisec_cfg.h"

static int hisec_send_msg_to_mpu_sync(void *hwdev, u8 cmd, void *buf_in,
				      u16 in_size, void *buf_out,
				      u16 *out_size)
{
	/* todo:: vf need to mpu com or not ? */
	return hinic3_msg_to_mgmt_sync(hwdev, HINIC3_MOD_CRYPT, cmd, buf_in,
				       in_size, buf_out, out_size,
				       0, HINIC3_CHANNEL_CRYPT);
}

int hisec_set_ipsec_offload_mode(void *hwdev, u8 offload_mode)
{
	struct hinic3_nic_io *nic_io = NULL;
	struct hisec_cmd_ipsec_offload_mode ipsec_cfg;
	u16 out_size = sizeof(ipsec_cfg);
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = hinic3_get_service_adapter(hwdev, SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	(void)memset_s(&ipsec_cfg, sizeof(ipsec_cfg), 0, sizeof(ipsec_cfg));
	ipsec_cfg.func_id = hinic3_global_func_id(hwdev);
	ipsec_cfg.offload_mode = offload_mode;

	err = hisec_send_msg_to_mpu_sync(hwdev,
					 HISEC_MPU_CMD_SET_IPSEC_OFFLOAD_MODE,
					 &ipsec_cfg, sizeof(ipsec_cfg),
					 &ipsec_cfg, &out_size);
	if (err || !out_size || ipsec_cfg.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set ipsec offload mode, err: %d, status: 0x%x, out size: 0x%x\n",
			err, ipsec_cfg.msg_head.status, out_size);
        	return -EINVAL;
	}

	return 0;
}

int hisec_get_crypto_stats(void *hwdev, struct hisec_crypto_stats *stats)
{
	struct hisec_cmd_cryptodev_stats cdev_stats;
	u16 out_size = sizeof(cdev_stats);
	struct hinic3_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !stats)
		return -EINVAL;

	nic_io = hinic3_get_service_adapter(hwdev, SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	(void)memset_s(&cdev_stats, sizeof(cdev_stats), 0, sizeof(cdev_stats));

	err = hisec_send_msg_to_mpu_sync(hwdev, HISEC_MPU_CMD_GET_CRYPTO_STATS,
					 &cdev_stats, sizeof(cdev_stats),
					 &cdev_stats, &out_size);
	if (err || !out_size || cdev_stats.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to get cryptodev statistics, err: %d, status: 0x%x, out size: 0x%x\n",
			err, cdev_stats.msg_head.status, out_size);
        	return -EFAULT;
	}

	(void)memcpy_s(stats, sizeof(*stats), &cdev_stats.stats, sizeof(*stats));

	return 0;
}

int hisec_flush_ipsec_resource(void *hwdev, u32 flush_flag)
{
	struct hinic3_nic_io *nic_io = NULL;
	struct hinic3_cmd_buf *cmd_buf = NULL;
	hisec_cmd_flush_ipsec_res_s *flush_buf = NULL;
	int ret = 0;
	u64 out_param = 0;

	if (!hwdev)
		return -EINVAL;

	nic_io = hinic3_get_service_adapter(hwdev, SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	cmd_buf = hinic3_alloc_cmd_buf(hwdev);
	if (!cmd_buf) {
		sdk_err(nic_io->dev_hdl, "Failed to alloc cmd buf\n");
        	return -ENOMEM;
	}

	cmd_buf->size = sizeof(*flush_buf);
	flush_buf = (hisec_cmd_flush_ipsec_res_s *)cmd_buf->buf;
	flush_buf->func_id = cpu_to_be16(hinic3_global_func_id(hwdev));
	flush_buf->flush_flag = cpu_to_be32(flush_flag);
	flush_buf->cmdhdr.dw0.bs.cmd_type = HISEC_NPU_CMD_FLUSH_IPSEC_RES;
	flush_buf->cmdhdr.dw0.value = cpu_to_be32(flush_buf->cmdhdr.dw0.value);

	ret = hinic3_cmdq_direct_resp(hwdev, HINIC3_MOD_CRYPT,
    				  HISEC_NPU_CMD_FLUSH_IPSEC_RES, cmd_buf,
    				  &out_param, 0, HINIC3_CHANNEL_CRYPT);
	if (ret || out_param) {
		sdk_err(nic_io->dev_hdl, "Failed to flush IPsec res, ret: %d, out_param: 0x%llx\n",
			ret, out_param);
        	ret = -EFAULT;
	}

	hinic3_free_cmd_buf(hwdev, cmd_buf);

	return ret;
}
